/*
 * Node.cpp
 *
 *  Created on: Nov 16, 2009
 *      Author: kevin
 */

#include <iostream>
#include <sstream>
using namespace std;

#include "node.h"
#include "filemanager/filemanager.h"

#include <Poco/Exception.h>
using Poco::Exception;

#include <Poco/Hash.h>
using Poco::Hash;

#include <Poco/Random.h>
using Poco::Random;

#include "Poco/Stopwatch.h"
using Poco::Timer;
using Poco::TimerCallback;

#include "Poco/Thread.h"
using Poco::Thread;

#include <iostream>
using namespace std;

#include "command/node/joinringcommand.h"
#include "command/network/runcommand.h"
#include "command/unknowncommand.h"
#include "command/node/sendinfocommand.h"
#include "command/node/reconnectcommand.h"
#include "command/node/sendidcommand.h"
#include "command/node/batchputcommand.h"
#include "command/node/batchgetcommand.h"

#include <Poco/Net/Socket.h>
#include <Poco/Net/DatagramSocket.h>
#include <Poco/Net/DNS.h>

#include <Poco/DOM/DOMParser.h>
using Poco::XML::DOMParser;
#include <Poco/DOM/DOMWriter.h>
using Poco::XML::DOMWriter;
#include <Poco/DOM/Element.h>
using Poco::XML::Element;
#include <Poco/DOM/NodeList.h>
using Poco::XML::NodeList;

using namespace Poco::Net;

using namespace Commands;

Node::Node() :
	fPort(42357),
	fId(fPort),
	fInRing(false),
	fRingName("cecilia"),
	fMaster(new Master()),
	commands( new CommandQueue() ),
	fSlave(0),
	fMcast(IPAddress("234.42.35.70"),42069, commands),
	//fDisp(*fSlave, &commands),
	fCmdFac(),
	fSthread("slave"),
	fMthread("master"),
	fMCthread("multicastconnector"),
	fNodeThread("node"),
	//fDispThread("dispatcher"),
	fAloneTimer(5000,0),
	fIdLock(0,1),
	fBroadThread("Broadcast thread"),
	fBroadcast(SocketAddress("234.42.35.70",0), "")
{
	fSlave = new Slave(fPort, commands);
	cout << "Node :: Creating Node" << endl;
	cout << "Node :: Starting Master thread" << endl;
	fMthread.start(*fMaster);
	cout << "Node :: Starting Slave thread" << endl;
	fSthread.start(*fSlave);
	cout << "Node :: Starting Node thread" << endl;
	fNodeThread.setOSPriority(Thread::getMaxOSPriority());
	fNodeThread.start(*this);
	cout << "Node :: Registering commands" << endl;
	fCmdFac.registerCommand(new SendInfoCommand(SocketAddress()) );
	fCmdFac.registerCommand(new ReconnectCommand(SocketAddress(), 0));
	fCmdFac.registerCommand(new SendIdCommand(0));
	fCmdFac.registerCommand(new BatchPutCommand(map<string,File>()));
	fCmdFac.registerCommand(new BatchGetCommand(map<string,File>()));
	cout << "Node :: Node Created" << endl;
}

Node::~Node()
{
	cout << "Node :: Destroying Node " << endl;
}

void Node::joinRing( string ring )
{

	cout << "Node :: Joining a ring" << endl;
	//JoinRingCommand joinRing(fRingName, 42357);
	try{
		string ringInfo = fMcast.requestRingInfo();
		DOMParser parser;
		Document* doc = parser.parseString(ringInfo);
		fRingName = ((Element*)(doc->getElementsByTagName("ring")->item(0)))->getAttribute("name");
		cout << "Node :: Recieved ringInfo with name :: " << fRingName << endl;
		Thread thread;
		thread.start(fId);
		cout << "Node :: Creating SendInfoCommand" << endl;
		SendInfoCommand* cmd = new SendInfoCommand(fId.getSocket()->address());
		/*
		 * TODO Dit moet naar idcollector: In Idcollector steken we een pointer naar onze commandQueue,
		 * die maakt een SendInfoCommand aan met zijn socketgegevens in
		 * (dit is waar de andere nodes naar moeten terugsturen), zet deze dan in de queue.
		 * De Node kan dan vervolgens deze poppen, stuurt deze via multicastconnector rond. Dan gaan andere
		 * Nodes hun ip/id combinatie terugsturen naar het adres in SendInfoCommand.
		 */
		cout << "Node :: Multicasting SendInfoCommand" << endl;
		fMcast.sendCommand(cmd->repr());
		cout << "Node :: Joining ID collector" << endl;
		thread.join();
		cout << "Node :: Recieved my ID :: "<< fId.getId() << endl;
		fMCthread.start(fMcast);
		fBroadcast.setName(fRingName);
		fBroadThread.start(fBroadcast);
	} catch (exception e){
		cout << "Node :: Ring does not exist yet" << endl;
		Thread thread;
		thread.start(fId);
		cout << "Node :: Checking ID :: " << fId.getId() << endl;
		//fMcast.sendRingInfo();
		fMCthread.start(fMcast);
		cout << "Node :: Starting Broadcast thread" << endl;
		fBroadcast.setName(fRingName);
		fBroadThread.start(fBroadcast);
	}

	_nodeid = fId.getId();
	_previd = fId.getPrevId();
	_nextid = fId.getNextId();

	cout << "Node :: Prev ::" << endl
		<< "\tID = " << fId.getPrevId() << endl
		<< "\tIP = " << fId.getPrevIP().toString() << endl;
	cout << "Node :: Next ::" << endl
		<< "\tID = " << fId.getNextId() << endl
		<< "\tIP = " << fId.getNextIP().toString() << endl;

	SocketAddress nextAddr = fId.getNextIP();
	cout << "Node :: Next node is at " << nextAddr.toString() << endl;
	join(nextAddr.host().toString(), nextAddr.port());
	cout << "Node :: Joined with " << nextAddr.toString() << endl;

	if (fId.getId() != fId.getPrevId())
	{
		SocketAddress recAddr("0.0.0.0",fPort);
		ReconnectCommand reconn(recAddr, fId.getPrevId());
		cout << "Node :: Created ReconnectCommand" << endl;

		fMaster->executeCommand(&reconn);
	}

	cout << "Node :: Ring Joined" << endl;
	//fMCthread.join();
	/*
	fRingName = ring;
	idCollector idc;
	JoinRingCommand joinRing(ring, idc.port());

	Thread idColl("idCollection");
	idColl.start(idc);

	 * --> This will be replaced, look in idCollector.cpp
	We let the timer run, we make our multicast thread sleep on regular intervals
	so that the timer can count. (if we don't do this, the timer does nothing)
	TODO: Stop the timer if we get a response from a node!
	/
	cout << "Starting the timer!" << endl;
	fAloneTimer.start(TimerCallback<Node>((*this) , &Node::onTimer));


	fMcast.sendCommand(joinRing.repr());
	fId = idc.getId();
	id_type prev = 34;//idc.getPrevID();
	cout << "Node :: Just Received my ID: " << fId << endl;

	fMCthread.start(fMcast);
	cout << endl;
	BAAAAAAAD
	join(next.toString(), fPort);
	
	send reconnect multicast (with previous ID, to meIP?!)
	 connect to nextIP

	cout << "Node :: Ring Joined" << endl;
	*/
}

void Node::join(string host, UInt16 port)
{
	//The master part of this node will join with the slave part of a node
	//at the given host/port
	cout <<"Node :: Join method" << endl;
	fMaster->join(host, port);
	SendIdCommand getid(_nodeid);
	fMaster->executeCommand(&getid);
	cout << "Node :: Sending out ID" << endl;
	id_type nid;
	string res = getid.result();
	istringstream(res) >> nid;
	setNextId(nid);
	cout << "Node :: Received ID :: '" << res << "' -> " << nid << endl;
	cout << "Node :: Join completed" << endl;
}

void Node::leave()
{
	cout << "Node :: Goint to try and leave " << endl;
	fMaster->leave();
	cout << "Node :: Leave completed" << endl;
}

void Node::put(string filename, File file)
{
	cout << "Node :: Putting a file " << endl;
	FileManager* fmgr = new FileManager();
	fmgr->addFile(filename, file);

	fMaster->put(filename);
	cout << "Node :: File put" << endl;
}

bool Node::get(string filename, File file)
{
	cout << "Node :: Getting a file " << endl;
	bool found = fMaster->get(filename, file);
	cout << "Node :: I got the file" << endl;
	return found;
}

void Node::run()
{
	cout << "Node :: Starting Node thread" << endl;
	while (true)
	{
		cout << "Node :: Going to handle my next command" << endl;
		Command* cmd = commands->pop();
		//FIXME See FIXME @ SendInfoCommand::makeCommand about the use of Node*
		//Command* cmd = fCmdFac.makeCommand( repr , this );
		cout << "Node :: New Command popped, going to execute" << endl;
		UnknownCommand* ucmd = dynamic_cast<UnknownCommand*>(cmd);
		if(ucmd){
			try {
				cout << "Node :: Command was an Unknown command, trying to figure out what command it actually is." << endl;
				NodeData* data = new NodeData();
				data->node_id = _nodeid;
				data->prev_id = _previd;
				data->next_id = _nextid;
				data->ringname = fRingName;
				data->origin = ucmd->originator();
				data->local = ucmd->destination();
				data->master = fMaster;
				data->slaveport = fPort;
				data->node = this;
				cout << "Node :: Going to try to reconstruct the command" << endl;
				Command* ncmd = fCmdFac.makeCommand( ucmd->repr(), data );
				cout << "Node :: Going to execute " << ncmd->name() << endl;
				ncmd->execute();
				cout << "Node :: Forwarding result of " << ncmd->name() << endl;
				ucmd->setResult(ncmd->result());
			} catch (NotImplemented ni) {
				cout << "Node :: Could not reconstruct the command, here is the representation:" << endl
					<< "Node :: " << ucmd->repr() << endl;
			}
		} else {
			cmd->execute();
		}

		cout << "Node :: End of While loop"<<endl;
	}

	cout << "Node :: Stopping Node thread" << endl;
}

Node::id_type Node::id()
{
	fIdLock.wait();
	return fId.getId();
}

bool Node::hasId()
{
	return fIdLock.tryWait(0);
}

void Node::printNodeInfo()
{
	cout << "Node :: INFO ::" << endl 
		<< "\tId = " << _nodeid << endl
		<< "\tPrev ID = " << _previd << endl
		<< "\tNext ID = " << _nextid << endl
		<< "\tNext IP = " << fMaster->getConnectedHostname() << endl;
}

/*
vector< pair< string, FileSize > > Node::listing()
{

}
*/
